The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.


Probably the most common use of expression data in Cytoscape is to set the visual attributes of the nodes in a network according to expression data. This creates a powerful visualization, portraying functional relation and experimental response at the same time. Here, we will walk through the steps for doing this.


Installation

if (!requireNamespace("BiocManager", quietly = TRUE))
  install.packages("BiocManager")
if(!"RCy3" %in% installed.packages())
  BiocManager::install("RCy3")

install.packages("httr")

Getting started

First, launch Cytoscape and keep it running whenever using RCy3. Confirm that you have everything installed and running:

library(RCy3)
cytoscapePing()
You are connected to Cytoscape!
cytoscapeVersionInfo()
      apiVersion cytoscapeVersion 
            "v1"          "3.8.2" 

Loading Network

  • The demo session is available here and can be opened using:
library(httr)
cys_url = "http://nrnb.org/data/galFilteredSimpleData.cys"
GET(cys_url, write_disk(tf <- tempfile(fileext = ".cys")))
Response [https://nrnb.org/data/galFilteredSimpleData.cys]
  Date: 2021-01-16 21:25
  Status: 200
  Content-Type: application/octet-stream
  Size: 1.4 MB
<ON DISK>  C:\WINDOWS\TEMP\RtmpGS7eWA\file459478b7504e.cys
openSession(tf)
Opening C:\WINDOWS\TEMP\RtmpGS7eWA\file459478b7504e.cys...
  • When the network first opens, the entire network is not visible because of the default zoom factor used. To see the whole network, we can use the fitContent function:
fitContent()

Label the Nodes

  • Zoom in on the network so that node labels are visible.
  • Check available column names.
getTableColumnNames('node')
 [1] "SUID"        "shared name" "name"        "selected"    "COMMON"      "gal1RGexp"   "gal4RGexp"   "gal80Rexp"   "gal1RGsig"   "gal4RGsig"   "gal80Rsig"  
  • We are going to use the COMMON name attribute to give the nodes useful names:
setNodeLabelMapping('COMMON')
style.name not specified, so updating "default" style.
NULL
  • Verify that the node labels on the network have changed to their common names.

Color the nodes

  • As we saw from the “getTableColumnNames” command, there is a column named gal80Rexp, which contains expression data. Let’s check the expression value range of the data in this column:
gal80rexp.score.table <- getTableColumns('node', "gal80Rexp")
gal80rexp.min <- min(gal80rexp.score.table, na.rm = T)
gal80rexp.max <- max(gal80rexp.score.table, na.rm = T)
print(gal80rexp.min)
[1] -1.373
print(gal80rexp.max)
[1] 3.126
  • The following produces a default gradient ranging from blue to red for gal80Rexp expression values. Notice that the nodes in the network change color.
setNodeColorMapping("gal80Rexp", c(-gal80rexp.max, 0, gal80rexp.max), c('#0000FF', '#FFFFFF', '#FF0000'))
style.name not specified, so updating "default" style.
NULL
  • The network should now look like this:

Set the Default Node Color

Note that the default node color of pale blue is close to the color range in the created palette. A useful trick is to choose a color outside this spectrum to distinguish nodes with no defined expression value and those with slight repression.

  • Here we choose a dark gray color for default node color:
setNodeColorDefault('#A9A9A9')
style.name not specified, so updating "default" style.
  • Zoom out on the network view to verify that a few nodes have been colored gray:
fitContent()

Set the Node Border

We imported both expression measurement values and corresponding significance values. We can use the significance values to change the border of nodes to highlight measurements we have confidence in.

Let’s create a thicker node border for nodes with gal80Rsig value less than 0.05. First, we need to get the min and max of values for gal80Rsig

gal80rsig.score.table <- getTableColumns('node', "gal80Rsig")
gal80rsig.min <- min(gal80rsig.score.table, na.rm = T)
gal80rsig.max <- max(gal80rsig.score.table, na.rm = T)
print(gal80rsig.min)
[1] 1.7403e-20
print(gal80rsig.max)
[1] 0.999999
setNodeBorderWidthMapping('gal80Rsig', c(gal80rsig.min, "0.0499999", "0.05", gal80rsig.max), c('10',"10",'0', '0'))
style.name not specified, so updating "default" style.
NULL

The node border now indicates significance, but since the default node border color is a pale grey, it is not very visible. We can change the default border color to a darker grey:

setNodeBorderColorDefault('#666666')
style.name not specified, so updating "default" style.

Fun with Charts

In addition to coloring the nodes, Cytoscape also provides the ability to draw charts and graphs on each node. For example, suppose we wanted to display a bar chart showing all of the expression values on each of our nodes?

  • To reset things a little, remove the mapping for Fill Color. First, let’s find out the property name for Fill Color:
getVisualPropertyNames()
  [1] "COMPOUND_NODE_PADDING"              "COMPOUND_NODE_SHAPE"                "DING_RENDERING_ENGINE_ROOT"         "EDGE"                              
  [5] "EDGE_BEND"                          "EDGE_CURVED"                        "EDGE_LABEL"                         "EDGE_LABEL_COLOR"                  
  [9] "EDGE_LABEL_FONT_FACE"               "EDGE_LABEL_FONT_SIZE"               "EDGE_LABEL_TRANSPARENCY"            "EDGE_LABEL_WIDTH"                  
 [13] "EDGE_LINE_TYPE"                     "EDGE_PAINT"                         "EDGE_SELECTED"                      "EDGE_SELECTED_PAINT"               
 [17] "EDGE_SOURCE_ARROW_SELECTED_PAINT"   "EDGE_SOURCE_ARROW_SHAPE"            "EDGE_SOURCE_ARROW_SIZE"             "EDGE_SOURCE_ARROW_UNSELECTED_PAINT"
 [21] "EDGE_STROKE_SELECTED_PAINT"         "EDGE_STROKE_UNSELECTED_PAINT"       "EDGE_TARGET_ARROW_SELECTED_PAINT"   "EDGE_TARGET_ARROW_SHAPE"           
 [25] "EDGE_TARGET_ARROW_SIZE"             "EDGE_TARGET_ARROW_UNSELECTED_PAINT" "EDGE_TOOLTIP"                       "EDGE_TRANSPARENCY"                 
 [29] "EDGE_UNSELECTED_PAINT"              "EDGE_VISIBLE"                       "EDGE_WIDTH"                         "NETWORK"                           
 [33] "NETWORK_ANNOTATION_SELECTION"       "NETWORK_BACKGROUND_PAINT"           "NETWORK_CENTER_X_LOCATION"          "NETWORK_CENTER_Y_LOCATION"         
 [37] "NETWORK_CENTER_Z_LOCATION"          "NETWORK_DEPTH"                      "NETWORK_EDGE_SELECTION"             "NETWORK_FORCE_HIGH_DETAIL"         
 [41] "NETWORK_HEIGHT"                     "NETWORK_NODE_LABEL_SELECTION"       "NETWORK_NODE_SELECTION"             "NETWORK_SCALE_FACTOR"              
 [45] "NETWORK_SIZE"                       "NETWORK_TITLE"                      "NETWORK_WIDTH"                      "NODE"                              
 [49] "NODE_BORDER_PAINT"                  "NODE_BORDER_STROKE"                 "NODE_BORDER_TRANSPARENCY"           "NODE_BORDER_WIDTH"                 
 [53] "NODE_CUSTOMGRAPHICS_1"              "NODE_CUSTOMGRAPHICS_2"              "NODE_CUSTOMGRAPHICS_3"              "NODE_CUSTOMGRAPHICS_4"             
 [57] "NODE_CUSTOMGRAPHICS_5"              "NODE_CUSTOMGRAPHICS_6"              "NODE_CUSTOMGRAPHICS_7"              "NODE_CUSTOMGRAPHICS_8"             
 [61] "NODE_CUSTOMGRAPHICS_9"              "NODE_CUSTOMGRAPHICS_POSITION_1"     "NODE_CUSTOMGRAPHICS_POSITION_2"     "NODE_CUSTOMGRAPHICS_POSITION_3"    
 [65] "NODE_CUSTOMGRAPHICS_POSITION_4"     "NODE_CUSTOMGRAPHICS_POSITION_5"     "NODE_CUSTOMGRAPHICS_POSITION_6"     "NODE_CUSTOMGRAPHICS_POSITION_7"    
 [69] "NODE_CUSTOMGRAPHICS_POSITION_8"     "NODE_CUSTOMGRAPHICS_POSITION_9"     "NODE_CUSTOMGRAPHICS_SIZE_1"         "NODE_CUSTOMGRAPHICS_SIZE_2"        
 [73] "NODE_CUSTOMGRAPHICS_SIZE_3"         "NODE_CUSTOMGRAPHICS_SIZE_4"         "NODE_CUSTOMGRAPHICS_SIZE_5"         "NODE_CUSTOMGRAPHICS_SIZE_6"        
 [77] "NODE_CUSTOMGRAPHICS_SIZE_7"         "NODE_CUSTOMGRAPHICS_SIZE_8"         "NODE_CUSTOMGRAPHICS_SIZE_9"         "NODE_CUSTOMPAINT_1"                
 [81] "NODE_CUSTOMPAINT_2"                 "NODE_CUSTOMPAINT_3"                 "NODE_CUSTOMPAINT_4"                 "NODE_CUSTOMPAINT_5"                
 [85] "NODE_CUSTOMPAINT_6"                 "NODE_CUSTOMPAINT_7"                 "NODE_CUSTOMPAINT_8"                 "NODE_CUSTOMPAINT_9"                
 [89] "NODE_DEPTH"                         "NODE_FILL_COLOR"                    "NODE_HEIGHT"                        "NODE_LABEL"                        
 [93] "NODE_LABEL_COLOR"                   "NODE_LABEL_FONT_FACE"               "NODE_LABEL_FONT_SIZE"               "NODE_LABEL_POSITION"               
 [97] "NODE_LABEL_TRANSPARENCY"            "NODE_LABEL_WIDTH"                   "NODE_NESTED_NETWORK_IMAGE_VISIBLE"  "NODE_PAINT"                        
[101] "NODE_SELECTED"                      "NODE_SELECTED_PAINT"                "NODE_SHAPE"                         "NODE_SIZE"                         
[105] "NODE_TOOLTIP"                       "NODE_TRANSPARENCY"                  "NODE_VISIBLE"                       "NODE_WIDTH"                        
[109] "NODE_X_LOCATION"                    "NODE_Y_LOCATION"                    "NODE_Z_LOCATION"                   
deleteStyleMapping("default", "NODE_FILL_COLOR")
  • Now change the default value to a lighter shade of grey so we can see our chart:
setNodeColorDefault('#D3D3D3')
style.name not specified, so updating "default" style.
  • The following indicates that we’re going to use the data from these three columns expression data (gal1RGexp, gal4RGexp, gal80Rexp) to create bar chart.
setNodeCustomBarChart(c("gal1RGexp", "gal4RGexp", "gal80Rexp"), "HEAT_STRIPS")
style.name not specified, so updating "default" style.

The network now shows heat strips for the three expression values, with colors chosen from a default Brewer palette.

LS0tDQp0aXRsZTogIlZpc3VhbGl6aW5nIEV4cHJlc3Npb24gRGF0YSINCmF1dGhvcjogIktvem8gTmlzaGlkYSwgS3Jpc3RpbmEgSGFuc3BlcnMsIEFsZXhhbmRlciBQaWNvIg0KZGF0ZTogJ2ByIFN5cy5EYXRlKClgJw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiANCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogbm9uZQ0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBldmFsPUZBTFNFDQopDQpgYGANCg0KKlRoZSBSIG1hcmtkb3duIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSBwdWxsZG93biBtZW51IGZvciogQ29kZSAqYXQgdGhlIHVwcGVyLXJpZ2h0LCBjaG9vc2UgIkRvd25sb2FkIFJtZCIsIG9yIFtkb3dubG9hZCB0aGUgUm1kIGZyb20gR2l0SHViXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL21hcHBpbmctZGF0YS5SbWQpLioNCg0KPGhyIC8+DQoNClByb2JhYmx5IHRoZSBtb3N0IGNvbW1vbiB1c2Ugb2YgZXhwcmVzc2lvbiBkYXRhIGluIEN5dG9zY2FwZSBpcyB0byBzZXQgdGhlIHZpc3VhbCBhdHRyaWJ1dGVzIG9mIHRoZSBub2RlcyBpbiBhIG5ldHdvcmsgYWNjb3JkaW5nIHRvIGV4cHJlc3Npb24gZGF0YS4gVGhpcyBjcmVhdGVzIGEgcG93ZXJmdWwgdmlzdWFsaXphdGlvbiwgcG9ydHJheWluZyBmdW5jdGlvbmFsIHJlbGF0aW9uIGFuZCBleHBlcmltZW50YWwgcmVzcG9uc2UgYXQgdGhlIHNhbWUgdGltZS4gSGVyZSwgd2Ugd2lsbCB3YWxrIHRocm91Z2ggdGhlIHN0ZXBzIGZvciBkb2luZyB0aGlzLg0KDQo8Y2VudGVyPg0KIVtdKGRhdGEvaW1nL21hcHBpbmctZGF0YS1maW5hbC5wbmcpDQo8L2NlbnRlcj4NCg0KPGhyIC8+DQoNCiMgSW5zdGFsbGF0aW9uDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkNCiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KaWYoISJSQ3kzIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKQ0KICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiUkN5MyIpDQoNCmluc3RhbGwucGFja2FnZXMoImh0dHIiKQ0KYGBgDQoNCiMgR2V0dGluZyBzdGFydGVkDQpGaXJzdCwgbGF1bmNoIEN5dG9zY2FwZSBhbmQga2VlcCBpdCBydW5uaW5nIHdoZW5ldmVyIHVzaW5nIFJDeTMuIENvbmZpcm0gdGhhdCB5b3UgaGF2ZSBldmVyeXRoaW5nIGluc3RhbGxlZCBhbmQgcnVubmluZzoNCmBgYHtyfQ0KbGlicmFyeShSQ3kzKQ0KY3l0b3NjYXBlUGluZygpDQpjeXRvc2NhcGVWZXJzaW9uSW5mbygpDQpgYGANCg0KIyBMb2FkaW5nIE5ldHdvcmsNCg0KLSBUaGUgZGVtbyBzZXNzaW9uIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cDovL25ybmIub3JnL2RhdGEvZ2FsRmlsdGVyZWRTaW1wbGVEYXRhLmN5cykgYW5kIGNhbiBiZSBvcGVuZWQgdXNpbmc6DQoNCmBgYHtyfQ0KbGlicmFyeShodHRyKQ0KY3lzX3VybCA9ICJodHRwOi8vbnJuYi5vcmcvZGF0YS9nYWxGaWx0ZXJlZFNpbXBsZURhdGEuY3lzIg0KR0VUKGN5c191cmwsIHdyaXRlX2Rpc2sodGYgPC0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuY3lzIikpKQ0Kb3BlblNlc3Npb24odGYpDQpgYGANCg0KLSBXaGVuIHRoZSBuZXR3b3JrIGZpcnN0IG9wZW5zLCB0aGUgZW50aXJlIG5ldHdvcmsgaXMgbm90IHZpc2libGUgYmVjYXVzZSBvZiB0aGUgZGVmYXVsdCB6b29tIGZhY3RvciB1c2VkLiBUbyBzZWUgdGhlIHdob2xlIG5ldHdvcmssIHdlIGNhbiB1c2UgdGhlIGBmaXRDb250ZW50IGAgZnVuY3Rpb246DQoNCmBgYHtyfQ0KZml0Q29udGVudCgpDQpgYGANCg0KIyBMYWJlbCB0aGUgTm9kZXMNCg0KLSBab29tIGluIG9uIHRoZSBuZXR3b3JrIHNvIHRoYXQgbm9kZSBsYWJlbHMgYXJlIHZpc2libGUuDQotIENoZWNrIGF2YWlsYWJsZSBjb2x1bW4gbmFtZXMuDQoNCmBgYHtyfQ0KZ2V0VGFibGVDb2x1bW5OYW1lcygnbm9kZScpDQpgYGANCg0KLSBXZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBDT01NT04gbmFtZSBhdHRyaWJ1dGUgdG8gZ2l2ZSB0aGUgbm9kZXMgdXNlZnVsIG5hbWVzOg0KDQpgYGB7cn0NCnNldE5vZGVMYWJlbE1hcHBpbmcoJ0NPTU1PTicpDQpgYGANCg0KLSBWZXJpZnkgdGhhdCB0aGUgbm9kZSBsYWJlbHMgb24gdGhlIG5ldHdvcmsgaGF2ZSBjaGFuZ2VkIHRvIHRoZWlyIGNvbW1vbiBuYW1lcy4NCg0KIyBDb2xvciB0aGUgbm9kZXMNCg0KLSBBcyB3ZSBzYXcgZnJvbSB0aGUgImdldFRhYmxlQ29sdW1uTmFtZXMiIGNvbW1hbmQsIHRoZXJlIGlzIGEgY29sdW1uIG5hbWVkICoqZ2FsODBSZXhwKiosIHdoaWNoIGNvbnRhaW5zIGV4cHJlc3Npb24gZGF0YS4gTGV0J3MgY2hlY2sgdGhlIGV4cHJlc3Npb24gdmFsdWUgcmFuZ2Ugb2YgdGhlIGRhdGEgaW4gdGhpcyBjb2x1bW46DQoNCmBgYHtyfQ0KZ2FsODByZXhwLnNjb3JlLnRhYmxlIDwtIGdldFRhYmxlQ29sdW1ucygnbm9kZScsICJnYWw4MFJleHAiKQ0KZ2FsODByZXhwLm1pbiA8LSBtaW4oZ2FsODByZXhwLnNjb3JlLnRhYmxlLCBuYS5ybSA9IFQpDQpnYWw4MHJleHAubWF4IDwtIG1heChnYWw4MHJleHAuc2NvcmUudGFibGUsIG5hLnJtID0gVCkNCnByaW50KGdhbDgwcmV4cC5taW4pDQpwcmludChnYWw4MHJleHAubWF4KQ0KYGBgDQoNCi0gVGhlIGZvbGxvd2luZyBwcm9kdWNlcyBhIGRlZmF1bHQgZ3JhZGllbnQgcmFuZ2luZyBmcm9tIGJsdWUgdG8gcmVkIGZvciAqKmdhbDgwUmV4cCoqIGV4cHJlc3Npb24gdmFsdWVzLiBOb3RpY2UgdGhhdCB0aGUgbm9kZXMgaW4gdGhlIG5ldHdvcmsgY2hhbmdlIGNvbG9yLg0KDQpgYGB7cn0NCnNldE5vZGVDb2xvck1hcHBpbmcoImdhbDgwUmV4cCIsIGMoLWdhbDgwcmV4cC5tYXgsIDAsIGdhbDgwcmV4cC5tYXgpLCBjKCcjMDAwMEZGJywgJyNGRkZGRkYnLCAnI0ZGMDAwMCcpKQ0KYGBgDQoNCi0gVGhlIG5ldHdvcmsgc2hvdWxkIG5vdyBsb29rIGxpa2UgdGhpczoNCg0KIVtdKGRhdGEvaW1nL05ldHdvcmtCcmV3ZXIucG5nKQ0KDQojIFNldCB0aGUgRGVmYXVsdCBOb2RlIENvbG9yDQoNCk5vdGUgdGhhdCB0aGUgZGVmYXVsdCBub2RlIGNvbG9yIG9mIHBhbGUgYmx1ZSBpcyBjbG9zZSB0byB0aGUgY29sb3IgcmFuZ2UgaW4gdGhlIGNyZWF0ZWQgcGFsZXR0ZS4NCkEgdXNlZnVsIHRyaWNrIGlzIHRvIGNob29zZSBhIGNvbG9yIG91dHNpZGUgdGhpcyBzcGVjdHJ1bSB0byBkaXN0aW5ndWlzaCBub2RlcyB3aXRoIG5vIGRlZmluZWQgZXhwcmVzc2lvbiB2YWx1ZSBhbmQgdGhvc2Ugd2l0aCBzbGlnaHQgcmVwcmVzc2lvbi4NCg0KLSBIZXJlIHdlIGNob29zZSBhIGRhcmsgZ3JheSBjb2xvciBmb3IgZGVmYXVsdCBub2RlIGNvbG9yOg0KDQpgYGB7cn0NCnNldE5vZGVDb2xvckRlZmF1bHQoJyNBOUE5QTknKQ0KYGBgDQoNCi0gWm9vbSBvdXQgb24gdGhlIG5ldHdvcmsgdmlldyB0byB2ZXJpZnkgdGhhdCBhIGZldyBub2RlcyBoYXZlIGJlZW4gY29sb3JlZCBncmF5Og0KDQpgYGB7cn0NCmZpdENvbnRlbnQoKQ0KYGBgDQoNCiMgU2V0IHRoZSBOb2RlIEJvcmRlcg0KDQpXZSBpbXBvcnRlZCBib3RoIGV4cHJlc3Npb24gbWVhc3VyZW1lbnQgdmFsdWVzIGFuZCBjb3JyZXNwb25kaW5nIHNpZ25pZmljYW5jZSB2YWx1ZXMuIA0KV2UgY2FuIHVzZSB0aGUgc2lnbmlmaWNhbmNlIHZhbHVlcyB0byBjaGFuZ2UgdGhlIGJvcmRlciBvZiBub2RlcyB0byBoaWdobGlnaHQgbWVhc3VyZW1lbnRzIHdlIGhhdmUgY29uZmlkZW5jZSBpbi4NCg0KTGV0J3MgY3JlYXRlIGEgdGhpY2tlciBub2RlIGJvcmRlciBmb3Igbm9kZXMgd2l0aCAqKmdhbDgwUnNpZyoqIHZhbHVlIGxlc3MgdGhhbiAwLjA1Lg0KRmlyc3QsIHdlIG5lZWQgdG8gZ2V0IHRoZSBtaW4gYW5kIG1heCBvZiB2YWx1ZXMgZm9yICoqZ2FsODBSc2lnKioNCg0KYGBge3J9DQpnYWw4MHJzaWcuc2NvcmUudGFibGUgPC0gZ2V0VGFibGVDb2x1bW5zKCdub2RlJywgImdhbDgwUnNpZyIpDQpnYWw4MHJzaWcubWluIDwtIG1pbihnYWw4MHJzaWcuc2NvcmUudGFibGUsIG5hLnJtID0gVCkNCmdhbDgwcnNpZy5tYXggPC0gbWF4KGdhbDgwcnNpZy5zY29yZS50YWJsZSwgbmEucm0gPSBUKQ0KcHJpbnQoZ2FsODByc2lnLm1pbikNCnByaW50KGdhbDgwcnNpZy5tYXgpDQpgYGANCg0KYGBge3J9DQpzZXROb2RlQm9yZGVyV2lkdGhNYXBwaW5nKCdnYWw4MFJzaWcnLCBjKGdhbDgwcnNpZy5taW4sICIwLjA0OTk5OTkiLCAiMC4wNSIsIGdhbDgwcnNpZy5tYXgpLCBjKCcxMCcsIjEwIiwnMCcsICcwJykpDQpgYGANCg0KVGhlIG5vZGUgYm9yZGVyIG5vdyBpbmRpY2F0ZXMgc2lnbmlmaWNhbmNlLCBidXQgc2luY2UgdGhlIGRlZmF1bHQgbm9kZSBib3JkZXIgY29sb3IgaXMgYSBwYWxlIGdyZXksIGl0IGlzIG5vdCB2ZXJ5IHZpc2libGUuIFdlIGNhbiBjaGFuZ2UgdGhlIGRlZmF1bHQgYm9yZGVyIGNvbG9yIHRvIGEgZGFya2VyIGdyZXk6DQoNCmBgYHtyfQ0Kc2V0Tm9kZUJvcmRlckNvbG9yRGVmYXVsdCgnIzY2NjY2NicpDQpgYGANCg0KIyBGdW4gd2l0aCBDaGFydHMNCg0KSW4gYWRkaXRpb24gdG8gY29sb3JpbmcgdGhlIG5vZGVzLCBDeXRvc2NhcGUgYWxzbyBwcm92aWRlcyB0aGUgYWJpbGl0eSB0byBkcmF3IGNoYXJ0cyBhbmQgZ3JhcGhzIG9uIGVhY2ggbm9kZS4NCkZvciBleGFtcGxlLCBzdXBwb3NlIHdlIHdhbnRlZCB0byBkaXNwbGF5IGEgYmFyIGNoYXJ0IHNob3dpbmcgYWxsIG9mIHRoZSBleHByZXNzaW9uIHZhbHVlcyBvbiBlYWNoIG9mIG91ciBub2Rlcz8NCg0KLSBUbyByZXNldCB0aGluZ3MgYSBsaXR0bGUsIHJlbW92ZSB0aGUgbWFwcGluZyBmb3IgRmlsbCBDb2xvci4gRmlyc3QsIGxldCdzIGZpbmQgb3V0IHRoZSBwcm9wZXJ0eSBuYW1lIGZvciBGaWxsIENvbG9yOg0KDQpgYGB7cn0NCmdldFZpc3VhbFByb3BlcnR5TmFtZXMoKQ0KYGBgDQoNCmBgYHtyfQ0KZGVsZXRlU3R5bGVNYXBwaW5nKCJkZWZhdWx0IiwgIk5PREVfRklMTF9DT0xPUiIpDQpgYGANCg0KLSBOb3cgY2hhbmdlIHRoZSBkZWZhdWx0IHZhbHVlIHRvIGEgbGlnaHRlciBzaGFkZSBvZiBncmV5IHNvIHdlIGNhbiBzZWUgb3VyIGNoYXJ0Og0KDQpgYGB7cn0NCnNldE5vZGVDb2xvckRlZmF1bHQoJyNEM0QzRDMnKQ0KYGBgDQoNCi0gVGhlIGZvbGxvd2luZyBpbmRpY2F0ZXMgdGhhdCB3ZSdyZSBnb2luZyB0byB1c2UgdGhlIGRhdGEgZnJvbSB0aGVzZSB0aHJlZSBjb2x1bW5zIGV4cHJlc3Npb24gZGF0YSAoZ2FsMVJHZXhwLCBnYWw0UkdleHAsIGdhbDgwUmV4cCkgdG8gY3JlYXRlIGJhciBjaGFydC4NCg0KYGBge3J9DQpzZXROb2RlQ3VzdG9tQmFyQ2hhcnQoYygiZ2FsMVJHZXhwIiwgImdhbDRSR2V4cCIsICJnYWw4MFJleHAiKSwgIkhFQVRfU1RSSVBTIikNCmBgYA0KDQpUaGUgbmV0d29yayBub3cgc2hvd3MgaGVhdCBzdHJpcHMgZm9yIHRoZSB0aHJlZSBleHByZXNzaW9uIHZhbHVlcywgd2l0aCBjb2xvcnMgY2hvc2VuIGZyb20gYSBkZWZhdWx0IEJyZXdlciBwYWxldHRlLiANCg0KIVtdKGRhdGEvaW1nL21hcHBpbmctZGF0YS1maW5hbC5wbmcpDQo=